{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению\n",
    "<center> Автор материала: Тетерников Илья (@tetelias)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>mlxtend</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Данная библиотека содержит модули расширения и вспомогательные инструменты для программных библиотек \n",
    "Python, предназначенных для анализа данных и машинного обучения.\n",
    "\n",
    "В данном материале будет описаны:\n",
    "\n",
    "* процесс установки;\n",
    "* методы отбора признаков;\n",
    "* прочие методы, доступные в этой библиотеке;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Установка</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Происходит стандартным для библиотеки Python образом:\n",
    "    \n",
    "    pip install mlxtend\n",
    "    \n",
    "Или через Anaconda:\n",
    "    \n",
    "    conda install -c conda-forge mlxtend"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Отбор признаков методом полного перебора</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сначала мы рассмотрим обертку для полного перебора всех возможных комбинаций признаков. Для этого воспользуемся алгоритмом ExhaustiveFeatureSelector, которому необходимо передать следующие параметры:\n",
    "* Модель; \n",
    "* Минимальное количество признаков(`min_features=`);\n",
    "* Максимальное количество признаков(`max_features=`);\n",
    "* Метрика оценки(`scoring=`);\n",
    "* Параметр кросс-валидации;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В качестве модели алгоритм принимает любую реализацию классификации или регрессии из scikit-learn. \n",
    "\n",
    "Среди метрик доступны {\"accuracy\", \"f1\", \"precision\", \"recall\", \"roc_auc\"} для классификации и {'mean_absolute_error', 'mean_squared_error', 'median_absolute_error', 'r2'} для регрессии.\n",
    "\n",
    "Рассмотрим простой пример на основе стандартного набора данных __Ирис__. Свойства `best_idx_` и `best_score_` метода ExhaustiveFeatureSelector позволяют получить список индексов в списке признаков и результат метрики для лучшего набора:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from mlxtend.feature_selection import ExhaustiveFeatureSelector as efs\n",
    "from sklearn.datasets import load_iris\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "\n",
    "iris = load_iris()\n",
    "X = iris.data\n",
    "y = iris.target\n",
    "\n",
    "knn = KNeighborsClassifier(n_neighbors=3)\n",
    "\n",
    "fs1 = efs(knn, \n",
    "          min_features=1,\n",
    "          max_features=4,\n",
    "          scoring='accuracy',\n",
    "          print_progress=True,\n",
    "          cv=5)\n",
    "\n",
    "fs1 = fs1.fit(X, y)\n",
    "\n",
    "print('Best accuracy score: %.2f' % fs1.best_score_)\n",
    "print('Best subset:', fs1.best_idx_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "С помощью свойства **subsets_** мы можем увидеть подробности каждого шага:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fs1.subsets_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Данный метод может также применяться в процессе подбора параметров моделей с помощью GridSearchCV. Для этого необходимо применение метода make_pipeline. Для извлечения лучшего набора признаков нужно указать **refit=True** в GridSearchCV:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from mlxtend.feature_selection import ExhaustiveFeatureSelector as efs\n",
    "from sklearn.datasets import load_iris\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.model_selection import GridSearchCV, train_test_split\n",
    "from sklearn.pipeline import make_pipeline\n",
    "\n",
    "iris = load_iris()\n",
    "X, y = iris.data, iris.target\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=13)\n",
    "\n",
    "logit = LogisticRegression(multi_class='multinomial', \n",
    "                           solver='lbfgs', \n",
    "                           random_state=123)\n",
    "\n",
    "fs1 = efs(estimator=logit, \n",
    "          min_features=2,\n",
    "          max_features=3,\n",
    "          scoring='accuracy',\n",
    "          print_progress=False,\n",
    "          clone_estimator=False,\n",
    "          cv=5,\n",
    "          n_jobs=1)\n",
    "\n",
    "pipe = make_pipeline(fs1, logit)\n",
    "\n",
    "param_grid = {'exhaustivefeatureselector__estimator__C': [0.1, 1.0, 10.0]}\n",
    "\n",
    "gs = GridSearchCV(estimator=pipe, \n",
    "                  param_grid=param_grid, \n",
    "                  scoring='accuracy', \n",
    "                  n_jobs=1, \n",
    "                  cv=5, \n",
    "                  verbose=1, \n",
    "                  refit=True)\n",
    "\n",
    "# run gridearch\n",
    "gs = gs.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Лучшие параметры GridSearchCV выводятся стандартным"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.best_params_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "А вот индексы лучших признаков отыскать не так просто:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.best_estimator_.steps[0][1].best_idx_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Отбор признаков методом последовательного перебора</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Полный перебор прост в исполнении, но количество вариантов растет пропорционально 2 в степени количества признаков. Для того, чтобы избежать такого массового перебора есть группа методов последовательного отбора признаков. Метод Sequential Forward Selection начинает с 0 признаков и выбирает тот, что максимально увеличивает заданную пользователем метрику. Затем к отобранному добавленяетсяием еще один и т.д.. Метод Sequential Backward Selection наоборот начинает с полного набора и отбрасывает по одному признаки менее всего положительно влияющие на заданную метрику. Есть еще надстройки над этими методами: Sequential Forward loating Selection и Sequential Backward Floating Selection. Они по сравнению с базовыми делают проверку, не улучшат ли уже отброшенные до этого признаки показатель метрики, если их все-таки добавить на текущем этапе работы алгоритма."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Необходимые параметры:\n",
    "* Модель;\n",
    "* Количество признаков, которое мы хотим получить на выходе, задаваемое через k_features;\n",
    "* Направление прохождения алгоритма: от нулевого(`forward=True`) или полного(`forward=False`) набора признаков;\n",
    "* Пытаться ли вернуть ранее отброшенные признаки: да(`floating=True`) или нет(`floating=False`);\n",
    "* Метрика оценки(`scoring=`);\n",
    "* Параметр кросс-валидации.\n",
    "\n",
    "Но к ним добавляется еще и кол-во признаков, которое мы хотим получить на выходе"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from mlxtend.feature_selection import SequentialFeatureSelector as sfs\n",
    "from sklearn.datasets import load_iris\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "\n",
    "iris = load_iris()\n",
    "X = iris.data\n",
    "y = iris.target\n",
    "knn = KNeighborsClassifier(n_neighbors=4)\n",
    "\n",
    "sfs1 = sfs(knn, \n",
    "           k_features=3, \n",
    "           forward=True, \n",
    "           floating=False, \n",
    "           verbose=2,\n",
    "           scoring='accuracy',\n",
    "           cv=0)\n",
    "\n",
    "sfs1 = sfs1.fit(X, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как и в предыдущий раз свойство **subsets_** позволяет увидеть подробности каждого шага:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sfs1.subsets_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Индексы лучшего набора признаков можно получить, используя свойство **`k_feature_idx_`**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sfs1.k_feature_idx_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Так же, как и в случае с полным перебором, данный метод можно использовать вместе с GridSearchCV. Но надо не забыть  указать в GridSearchCV **refit=True**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import mlxtend\n",
    "from mlxtend.feature_selection import SequentialFeatureSelector as sfs\n",
    "from sklearn.datasets import load_iris\n",
    "from sklearn.model_selection import GridSearchCV, train_test_split\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.pipeline import Pipeline\n",
    "\n",
    "iris = load_iris()\n",
    "X, y = iris.data, iris.target\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=13)\n",
    "\n",
    "knn = KNeighborsClassifier(n_neighbors=2)\n",
    "\n",
    "sfs1 = sfs(estimator=knn, \n",
    "           k_features=3,\n",
    "           forward=True, \n",
    "           floating=False, \n",
    "           scoring='accuracy',\n",
    "           cv=5)\n",
    "\n",
    "pipe = Pipeline([('sfs', sfs1), \n",
    "                 ('knn', knn)])\n",
    "\n",
    "param_grid = [\n",
    "  {'sfs__k_features': [1, 2, 3, 4],\n",
    "   'sfs__estimator__n_neighbors': [1, 2, 3, 4]}\n",
    "  ]\n",
    "\n",
    "gs = GridSearchCV(estimator=pipe, \n",
    "                  param_grid=param_grid, \n",
    "                  scoring='accuracy', \n",
    "                  n_jobs=1, \n",
    "                  cv=5,                   \n",
    "                  verbose=1,\n",
    "                  refit=True)\n",
    "\n",
    "# run gridearch\n",
    "gs = gs.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И опять получение лучшего набора признаков не выглядит элегантно:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.best_estimator_.steps[0][1].k_feature_idx_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <center>Другие интересные методы библиотеки</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Библиотека также содержит единственную на Python реализацию алгоритма Априори для построения правил ассоциативности: \n",
    "очень простого, но обладающего исключительной интерпретируемостью метода исследования данных. Помимо [небольшого пояснения от автора](http://rasbt.github.io/mlxtend/user_guide/frequent_patterns/apriori/) рекомендую к прочтению вот [эту статью](http://pbpython.com/market-basket-analysis.html).\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И напоследок: библиотека также имеет средства работы с текстом, включая возможность обрабатывать смайлики. Можно пытаться делать свой шаблон через библиотеку __re__ или просто использовать"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from mlxtend.text import tokenizer_emoticons\n",
    "\n",
    "tokenizer_emoticons('</a>This :) is :( a test ;-)!')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Можно извлекать смайлики не только отдельно, но и вместе с текстом:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from mlxtend.text import tokenizer_words_and_emoticons\n",
    "\n",
    "tokenizer_words_and_emoticons('</a>This :) is :( a test :-)!')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Ссылки\n",
    "* [Документация](http://rasbt.github.io/mlxtend/) mlxtend\n",
    "* [Репозитарий на Github](https://github.com/rasbt/mlxtend)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
